﻿/* 
 * Copyright (c) 2011-13 Andrey Bulygin
 * 
 * This file is part of Interceptor.
 * Interceptor is free software: you can redistribute it and/or modify it under the terms of the GNU 
 * Library General Public License as published by the Free Software Foundation, either version 3 of the 
 * License, or (at your option) any later version.
 * 
 * Interceptor is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without 
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See 
 * the GNU Library General Public License for more details. You should have received a copy of the GNU 
 * Library General Public License along with Interceptor. If not, write to the Free Software Foundation, Inc., 59 
 * Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;
using Interceptor.Core.Exceptions;
using Interceptor.Core.Objects;

namespace Interceptor.Core
{
	internal class InterceptorValidator
	{
		static Dictionary<ErrorCode, string> _errorDescr = new Dictionary<ErrorCode,string>();
		static InterceptorValidator()
		{
			_errorDescr = new Dictionary<ErrorCode, string>();

			_errorDescr.Add(ErrorCode.InvalidParametersCount, "Invalid parameters count. First parameter should be intercepted Target, second parameter should be interception Delegate or inerception Context.");
			_errorDescr.Add(ErrorCode.InvalidTargetType, "Invalid first parameter type (Target parameter). Don't use types Batch<>, Skip<>, Ignore<> or Delegate as a Target parameter type.");
			_errorDescr.Add(ErrorCode.InvalidContextType, "Invalid second parameter type (Context/Delegate parameter). Second parameter should be: Invoker<> or an inheritor of MulticastDelegate.");
			_errorDescr.Add(ErrorCode.InvalidInvokerGenericParameter, "Invalid second parameter type (Context/Delegate parameter). Generic parameter of Invoker<> type should be an inheritor of MulticastDelegate.");
			_errorDescr.Add(ErrorCode.ReturnTypeDelegateInvokerError, "The return type of delegate and interceptor method does not match.");
			_errorDescr.Add(ErrorCode.ParametersLengthDelegateInvokerError, "Delegate Parameters count and interceptor method not batch parameters count are not the same.");
			_errorDescr.Add(ErrorCode.InvalidParameterTypeDelegateInvokerError, "Please check all delegate parameters types and interceptor method parameters types. There was detected an error: the types of parameters does not match.");
			_errorDescr.Add(ErrorCode.InvalidGenericParameterUsage, "There was detected the generic parameter which is not used in method declaration. Please remove this generic parameter.");
			_errorDescr.Add(ErrorCode.TargetIsByRef, "First parameter (Target) could not be 'out' or 'ref' parameter.");
			_errorDescr.Add(ErrorCode.ContextIsByRef, "Second parameter (Delegate/Context) could not be 'out' or 'ref' parameter.");
			_errorDescr.Add(ErrorCode.BatchGenericParameterIsGenericParameter, "You have invalid batch parameter definition. Generic argument of types Ignore<>, Skip<> or Batch<> could not be generic parameters. So, this definition is invalid: void Method<T>(TTarget t, TDelegate d, Batch<T> p).");
		}
		private InterceptorValidator()
		{
		}

		private ValidationError CreateError(MethodInfo method, ErrorCode errorCode)
		{
			return new ValidationError(GetInterceptorDefinition(method), errorCode, _errorDescr[errorCode]);
		}
		public static ValidationError[] Validate(MethodInfo itcMethod)
		{
			return new InterceptorValidator().ValidateMethod(itcMethod);
		}
		private ValidationError[] ValidateMethod(MethodInfo itcMethod)
		{
			var errors = new List<ValidationError>();
			var parameters = itcMethod.GetParameters();
			if (parameters.Length < 2)
				errors.Add(CreateError(itcMethod, ErrorCode.InvalidParametersCount));

			ItcType itcType = ItcType.Delegate;
			if (errors.Count == 0)
			{
				errors.AddRange(ValidateTarget(itcMethod, parameters[0]));
				errors.AddRange(ValidateContext(itcMethod, parameters[1].ParameterType, out itcType));
			}
			if (errors.Count == 0 && itcType == ItcType.Invoker)
			{
				errors.AddRange(ValidateInvokerGenericParameter(itcMethod, parameters[1].ParameterType.GetGenericArguments()[0]));
			}
			if (errors.Count == 0)
			{
				var itcMethodDefinition = new ItcMethodDefinition(itcMethod);
				if (itcType == ItcType.Invoker || itcType == ItcType.Delegate)
				{
					Type delegateType = itcType == ItcType.Invoker
														? parameters[1].ParameterType.GetGenericArguments()[0]
														: parameters[1].ParameterType;
					var delegateMethodInfo = delegateType.GetMethod("Invoke");
					errors.AddRange(ValidateParameters(itcMethodDefinition, delegateMethodInfo.ReturnType, delegateMethodInfo.GetParameters()));
				}
				if (errors.Count == 0)
				{
					errors.AddRange(ValidateGenericInterceptorParameters(itcMethodDefinition));
				}
			}
			
			return errors.ToArray();
		}

		private IEnumerable<ValidationError> ValidateGenericInterceptorParameters(ItcMethodDefinition itcMethod)
		{
			var errors = new List<ValidationError>();
			if (itcMethod.Method.IsGenericMethod)
			{
				Type[] genericArgs = itcMethod.Method.GetGenericArguments();
				foreach (var genericArg in genericArgs)
				{
					if (
						itcMethod.ReturnType != genericArg &&
						itcMethod.TargetType != genericArg &&
						itcMethod.Parameters.Count(x => x.ParameterInfo.ParameterType == genericArg) == 0 &&
						(!itcMethod.TargetType.IsGenericType ||
							!itcMethod.TargetType.GetGenericArguments().Any(x=>x == genericArg)
						))
					{
						errors.Add(CreateError(itcMethod.Method, ErrorCode.InvalidGenericParameterUsage));
						break;
					}
				}
			}
			return errors.ToArray();
		}

		private IEnumerable<ValidationError> ValidateParameters(ItcMethodDefinition itcMethod, Type delegateReturnType, ParameterInfo[] delegateParameters)
		{
			var errors = new List<ValidationError>();
			if (delegateReturnType != itcMethod.ReturnType)
				errors.Add(CreateError(itcMethod.Method, ErrorCode.ReturnTypeDelegateInvokerError));

			// validate not batch parameters
			ParameterInfo[] itcParameters = itcMethod.Parameters.Where(x=>!x.IsBatch).Select(x => x.ParameterInfo).ToArray();

			if (delegateParameters.Length != itcParameters.Length)
				errors.Add(CreateError(itcMethod.Method, ErrorCode.ParametersLengthDelegateInvokerError));

			if (errors.Count == 0)
			{
				for (int i = 0; i < delegateParameters.Length; i++)
				{
					if (delegateParameters[i].ParameterType != itcParameters[i].ParameterType)
					{
						errors.Add(CreateError(itcMethod.Method, ErrorCode.InvalidParameterTypeDelegateInvokerError));
						break;
					}
				}
			}

			// validate batch parameters
			if (errors.Count == 0)
			{
				ItcParameterDefinition[] batchParams = itcMethod.Parameters.Where(x => x.IsBatch).ToArray();
				foreach (var param in batchParams)
				{
					if (param.BatchType.IsGenericParameter)
					{
						errors.Add(CreateError(itcMethod.Method, ErrorCode.BatchGenericParameterIsGenericParameter));
						break;
					}
				}
			}
			return errors.ToArray();
		}

		private IEnumerable<ValidationError> ValidateInvokerGenericParameter(MethodInfo itcMethod, Type type)
		{
			var errors = new List<ValidationError>();
			if (type.BaseType != typeof(MulticastDelegate))
			{
				errors.Add(CreateError(itcMethod, ErrorCode.InvalidInvokerGenericParameter));
			}
			return errors.ToArray();
		}

		private IEnumerable<ValidationError> ValidateContext(MethodInfo itcMethod, Type parameterType, out ItcType itcType)
		{

			var errors = new List<ValidationError>();
			itcType = ItcType.Unknown;

			Type checkType = parameterType.IsGenericType
									? parameterType.GetGenericTypeDefinition()
									: parameterType;
			checkType = checkType.IsByRef
									? checkType.GetElementType()
									: checkType;

			if (checkType == typeof(Invoker<>))
			{
				itcType = ItcType.Invoker;
			}
			else if (checkType.BaseType == typeof(MulticastDelegate))
			{
				itcType = ItcType.Delegate;
			}
			else
			{
				errors.Add(CreateError(itcMethod, ErrorCode.InvalidContextType));
			}
			if (parameterType.IsByRef)
				errors.Add(CreateError(itcMethod, ErrorCode.ContextIsByRef));
			return errors.ToArray();
		}


		private ValidationError[] ValidateTarget(MethodInfo itcMethod, ParameterInfo parameterInfo)
		{
			var errors = new List<ValidationError>();
			Type checkType = parameterInfo.ParameterType.IsGenericType
												? parameterInfo.ParameterType.GetGenericTypeDefinition()
												: parameterInfo.ParameterType;

			if (checkType == typeof(Batch<>) ||
				checkType == typeof(Skip<>) ||
				checkType == typeof(Ignore<>) ||
				checkType.BaseType == typeof(MulticastDelegate) ||
				checkType == typeof(MulticastDelegate) ||
				checkType == typeof(Delegate))
					errors.Add(CreateError(itcMethod, ErrorCode.InvalidTargetType));

			if (parameterInfo.ParameterType.IsByRef)
				errors.Add(CreateError(itcMethod, ErrorCode.TargetIsByRef));

			return errors.ToArray();
		}

		private string GetInterceptorDefinition(MethodInfo itcMethod)
		{
			return String.Format("{0}({1}): {2}", itcMethod.Name, String.Join(", ", itcMethod.GetParameters().Select(x => x.ParameterType.Name)), itcMethod.ReturnType.Name);
		}
	}
}
